/-
Copyright (c) 2014 Parikshit Khanna. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Parikshit Khanna, Jeremy Avigad, Leonardo de Moura, Floris van Doorn, Mario Carneiro,
  Kim Morrison, Jannis Limperg
-/
module

prelude
public import Init.Data.List.Range
public import Init.Data.List.Impl
import all Init.Data.List.Attach
public import Init.Data.Fin.Lemmas

public section

/-!
Lemmas about `List.findSome?`, `List.find?`, `List.findIdx`, `List.findIdx?`, `List.idxOf`,
and `List.lookup`.
-/

set_option linter.listVariables true -- Enforce naming conventions for `List`/`Array`/`Vector` variables.
set_option linter.indexVariables true -- Enforce naming conventions for index variables.


namespace List

open Nat

/-! ### findSome? -/

@[simp] theorem findSome?_cons_of_isSome {l} (h : (f a).isSome) : findSome? f (a :: l) = f a := by
  simp only [findSome?]
  split <;> simp_all

@[simp] theorem findSome?_cons_of_isNone {l} (h : (f a).isNone) : findSome? f (a :: l) = findSome? f l := by
  simp only [findSome?]
  split <;> simp_all

theorem exists_of_findSome?_eq_some {l : List α} {f : α → Option β} (w : l.findSome? f = some b) :
    ∃ a, a ∈ l ∧ f a = some b := by
  induction l with
  | nil => simp_all
  | cons h l ih =>
    simp_all only [findSome?_cons, mem_cons, exists_eq_or_imp]
    split at w <;> simp_all

@[simp, grind =] theorem findSome?_eq_none_iff : findSome? p l = none ↔ ∀ x ∈ l, p x = none := by
  induction l <;> simp [findSome?_cons]; split <;> simp [*]

@[simp] theorem findSome?_isSome_iff {f : α → Option β} {l : List α} :
    (l.findSome? f).isSome ↔ ∃ x, x ∈ l ∧ (f x).isSome := by
  induction l with
  | nil => simp
  | cons x xs ih =>
    simp only [findSome?_cons]
    split <;> simp_all

theorem findSome?_eq_some_iff {f : α → Option β} {l : List α} {b : β} :
    l.findSome? f = some b ↔ ∃ l₁ a l₂, l = l₁ ++ a :: l₂ ∧ f a = some b ∧ ∀ x ∈ l₁, f x = none := by
  induction l with
  | nil => simp
  | cons p l ih =>
    simp only [findSome?_cons]
    split <;> rename_i b' h
    · simp only [Option.some.injEq, exists_and_right]
      constructor
      · rintro rfl
        exact ⟨[], p, ⟨l, rfl⟩, h, by simp⟩
      · rintro ⟨(⟨⟩ | ⟨p', l₁⟩), a, ⟨l₂, h₁⟩, h₂, h₃⟩
        · simp only [nil_append, cons.injEq] at h₁
          apply Option.some.inj
          simp [← h, ← h₂, h₁.1]
        · simp only [cons_append, cons.injEq] at h₁
          obtain ⟨rfl, rfl⟩ := h₁
          specialize h₃ p
          simp_all
    · rw [ih]
      constructor
      · rintro ⟨l₁, a, l₂, rfl, h₁, h₂⟩
        refine ⟨p :: l₁, a, l₂, rfl, h₁, ?_⟩
        intro a w
        simp at w
        rcases w with rfl | w
        · exact h
        · exact h₂ _ w
      · rintro ⟨l₁, a, l₂, h₁, h₂, h₃⟩
        rcases l₁ with (⟨⟩ | ⟨a', l₁⟩)
        · simp_all
        · simp only [cons_append, cons.injEq] at h₁
          obtain ⟨⟨rfl, rfl⟩, rfl⟩ := h₁
          exact ⟨l₁, a, l₂, rfl, h₂, fun a' w => h₃ a' (mem_cons_of_mem p w)⟩

@[simp, grind =] theorem findSome?_guard {l : List α} : findSome? (Option.guard p) l = find? p l := by
  induction l with
  | nil => simp
  | cons x xs ih =>
    simp [findSome?, find?]
    split <;> rename_i h
    · simp only [Option.guard_eq_some_iff] at h
      obtain ⟨rfl, h⟩ := h
      simp [h]
    · simp only [Option.guard_eq_none_iff] at h
      simp [ih, h]

theorem find?_eq_findSome?_guard {l : List α} : find? p l = findSome? (Option.guard p) l :=
  findSome?_guard.symm

@[simp, grind =] theorem head?_filterMap {f : α → Option β} {l : List α} : (l.filterMap f).head? = l.findSome? f := by
  induction l with
  | nil => simp
  | cons x xs ih =>
    simp only [filterMap_cons, findSome?_cons]
    split <;> simp [*]

@[simp, grind =] theorem head_filterMap {f : α → Option β} {l : List α} (h) :
    (l.filterMap f).head h = (l.findSome? f).get (by simp_all [Option.isSome_iff_ne_none]) := by
  simp [head_eq_iff_head?_eq_some]

@[simp, grind =] theorem getLast?_filterMap {f : α → Option β} {l : List α} : (l.filterMap f).getLast? = l.reverse.findSome? f := by
  rw [getLast?_eq_head?_reverse]
  simp [← filterMap_reverse]

@[simp, grind =] theorem getLast_filterMap {f : α → Option β} {l : List α} (h) :
    (l.filterMap f).getLast h = (l.reverse.findSome? f).get (by simp_all [Option.isSome_iff_ne_none]) := by
  simp [getLast_eq_iff_getLast?_eq_some]

@[simp, grind _=_] theorem map_findSome? {f : α → Option β} {g : β → γ} {l : List α} :
    (l.findSome? f).map g = l.findSome? (Option.map g ∘ f) := by
  induction l <;> simp [findSome?_cons]; split <;> simp [*]

@[grind _=_]
theorem findSome?_map {f : β → γ} {l : List β} : findSome? p (l.map f) = l.findSome? (p ∘ f) := by
  induction l with
  | nil => simp
  | cons x xs ih =>
    simp only [map_cons, findSome?]
    split <;> simp_all

@[grind =]
theorem head_flatten {L : List (List α)} (h : ∃ l, l ∈ L ∧ l ≠ []) :
    (flatten L).head (by simpa using h) = (L.findSome? head?).get (by simpa using h) := by
  simp [head_eq_iff_head?_eq_some, head?_flatten]

@[grind =]
theorem getLast_flatten {L : List (List α)} (h : ∃ l, l ∈ L ∧ l ≠ []) :
    (flatten L).getLast (by simpa using h) =
      (L.reverse.findSome? getLast?).get (by simpa using h) := by
  simp [getLast_eq_iff_getLast?_eq_some, getLast?_flatten]

@[grind =]
theorem findSome?_replicate : findSome? f (replicate n a) = if n = 0 then none else f a := by
  cases n with
  | zero => simp
  | succ n =>
    simp only [replicate_succ, findSome?_cons]
    split <;> simp_all

@[simp] theorem findSome?_replicate_of_pos (h : 0 < n) : findSome? f (replicate n a) = f a := by
  simp [findSome?_replicate, Nat.ne_of_gt h]

-- Argument is unused, but used to decide whether `simp` should unfold.
@[simp] theorem findSome?_replicate_of_isSome (_ : (f a).isSome) : findSome? f (replicate n a) = if n = 0 then none else f a := by
  simp [findSome?_replicate]

@[simp] theorem findSome?_replicate_of_isNone (h : (f a).isNone) : findSome? f (replicate n a) = none := by
  rw [Option.isNone_iff_eq_none] at h
  simp [findSome?_replicate, h]

theorem Sublist.findSome?_isSome {l₁ l₂ : List α} (h : l₁ <+ l₂) :
    (l₁.findSome? f).isSome → (l₂.findSome? f).isSome := by
  induction h with
  | slnil => simp
  | cons a h ih
  | cons₂ a h ih =>
    simp only [findSome?]
    split
    · simp_all
    · exact ih

grind_pattern Sublist.findSome?_isSome => l₁ <+ l₂, l₁.findSome? f
grind_pattern Sublist.findSome?_isSome => l₁ <+ l₂, l₂.findSome? f

theorem Sublist.findSome?_eq_none {l₁ l₂ : List α} (h : l₁ <+ l₂) :
    l₂.findSome? f = none → l₁.findSome? f = none := by
  simp only [List.findSome?_eq_none_iff]
  exact fun w x m => w x (Sublist.mem m h)

theorem IsPrefix.findSome?_eq_some {l₁ l₂ : List α} {f : α → Option β} (h : l₁ <+: l₂) :
    List.findSome? f l₁ = some b → List.findSome? f l₂ = some b := by
  rw [IsPrefix] at h
  obtain ⟨t, rfl⟩ := h
  simp +contextual [findSome?_append]

grind_pattern IsPrefix.findSome?_eq_some => l₁ <+: l₂, l₁.findSome? f, some b
grind_pattern IsPrefix.findSome?_eq_some => l₁ <+: l₂, l₂.findSome? f, some b

theorem IsPrefix.findSome?_eq_none {l₁ l₂ : List α} {f : α → Option β} (h : l₁ <+: l₂) :
    List.findSome? f l₂ = none → List.findSome? f l₁ = none :=
  h.sublist.findSome?_eq_none

grind_pattern IsPrefix.findSome?_eq_none => l₁ <+: l₂, l₂.findSome? f
grind_pattern IsPrefix.findSome?_eq_none => l₁ <+: l₂, l₁.findSome? f

theorem IsSuffix.findSome?_eq_none {l₁ l₂ : List α} {f : α → Option β} (h : l₁ <:+ l₂) :
    List.findSome? f l₂ = none → List.findSome? f l₁ = none :=
  h.sublist.findSome?_eq_none

grind_pattern IsSuffix.findSome?_eq_none => l₁ <+: l₂, l₂.findSome? f
grind_pattern IsSuffix.findSome?_eq_none => l₁ <+: l₂, l₁.findSome? f

theorem IsInfix.findSome?_eq_none {l₁ l₂ : List α} {f : α → Option β} (h : l₁ <:+: l₂) :
    List.findSome? f l₂ = none → List.findSome? f l₁ = none :=
  h.sublist.findSome?_eq_none

grind_pattern IsInfix.findSome?_eq_none => l₁ <+: l₂, l₂.findSome? f
grind_pattern IsInfix.findSome?_eq_none => l₁ <+: l₂, l₁.findSome? f

/-! ### find? -/

@[simp] theorem find?_cons_of_pos {l} (h : p a) : find? p (a :: l) = some a := by
  simp [find?, h]

@[simp] theorem find?_cons_of_neg {l} (h : ¬p a) : find? p (a :: l) = find? p l := by
  simp [find?, h]

@[simp, grind =] theorem find?_eq_none : find? p l = none ↔ ∀ x ∈ l, ¬ p x := by
  induction l <;> simp [find?_cons]; split <;> simp [*]

theorem find?_eq_some_iff_append :
    xs.find? p = some b ↔ p b ∧ ∃ as bs, xs = as ++ b :: bs ∧ ∀ a ∈ as, !p a := by
  induction xs with
  | nil => simp
  | cons x xs ih =>
    simp only [find?_cons, exists_and_right]
    split <;> rename_i h
    · simp only [Option.some.injEq]
      constructor
      · rintro rfl
        exact ⟨h, [], ⟨xs, rfl⟩, by simp⟩
      · rintro ⟨-, ⟨as, ⟨⟨bs, h₁⟩, h₂⟩⟩⟩
        cases as with
        | nil => simp_all
        | cons a as =>
          specialize h₂ a mem_cons_self
          simp only [cons_append] at h₁
          obtain ⟨rfl, -⟩ := h₁
          simp_all
    · simp only [ih, Bool.not_eq_eq_eq_not, Bool.not_true, exists_and_right, and_congr_right_iff]
      intro pb
      constructor
      · rintro ⟨as, ⟨⟨bs, rfl⟩, h₁⟩⟩
        refine ⟨x :: as, ⟨⟨bs, rfl⟩, ?_⟩⟩
        intro a m
        simp at m
        obtain (rfl|m) := m
        · exact h
        · exact h₁ a m
      · rintro ⟨as, ⟨bs, h₁⟩, h₂⟩
        cases as with
        | nil => simp_all
        | cons a as =>
          refine ⟨as, ⟨⟨bs, ?_⟩, fun a m => h₂ a (mem_cons_of_mem _ m)⟩⟩
          cases h₁
          simp

@[simp]
theorem find?_cons_eq_some : (a :: xs).find? p = some b ↔ (p a ∧ a = b) ∨ (!p a ∧ xs.find? p = some b) := by
  rw [find?_cons]
  split <;> simp_all

@[simp, grind =] theorem find?_isSome {xs : List α} {p : α → Bool} : (xs.find? p).isSome ↔ ∃ x, x ∈ xs ∧ p x := by
  induction xs with
  | nil => simp
  | cons x xs ih =>
    simp only [find?_cons, mem_cons, exists_eq_or_imp]
    split <;> simp_all

@[grind →]
theorem find?_some : ∀ {l}, find? p l = some a → p a
  | b :: l, H => by
    by_cases h : p b <;> simp [find?, h] at H
    · exact H ▸ h
    · exact find?_some H

@[grind →]
theorem mem_of_find?_eq_some : ∀ {l}, find? p l = some a → a ∈ l
  | b :: l, H => by
    by_cases h : p b <;> simp [find?, h] at H
    · exact H ▸ .head _
    · exact .tail _ (mem_of_find?_eq_some H)

theorem get_find?_mem {xs : List α} {p : α → Bool} (h) : (xs.find? p).get h ∈ xs := by
  induction xs with
  | nil => simp at h
  | cons x xs ih =>
    simp only [find?_cons]
    by_cases h : p x
    · simp [h]
    · simp only [h]
      right
      apply ih

grind_pattern get_find?_mem => (xs.find? p).get h

@[simp, grind =] theorem find?_filter {xs : List α} {p : α → Bool} {q : α → Bool} :
    (xs.filter p).find? q = xs.find? (fun a => p a ∧ q a) := by
  induction xs with
  | nil => simp
  | cons x xs ih =>
    simp only [filter_cons]
    split <;>
    · simp only [find?_cons]
      split <;> simp_all

@[simp, grind =] theorem head?_filter {p : α → Bool} {l : List α} : (l.filter p).head? = l.find? p := by
  rw [← filterMap_eq_filter, head?_filterMap, findSome?_guard]

@[simp, grind =] theorem head_filter {p : α → Bool} {l : List α} (h) :
    (l.filter p).head h = (l.find? p).get (by simp_all [Option.isSome_iff_ne_none]) := by
  simp [head_eq_iff_head?_eq_some]

@[simp, grind =] theorem getLast?_filter {p : α → Bool} {l : List α} : (l.filter p).getLast? = l.reverse.find? p := by
  rw [getLast?_eq_head?_reverse]
  simp [← filter_reverse]

@[simp, grind =] theorem getLast_filter {p : α → Bool} {l : List α} (h) :
    (l.filter p).getLast h = (l.reverse.find? p).get (by simp_all [Option.isSome_iff_ne_none]) := by
  simp [getLast_eq_iff_getLast?_eq_some]

@[simp, grind =] theorem find?_filterMap {xs : List α} {f : α → Option β} {p : β → Bool} :
    (xs.filterMap f).find? p = (xs.find? (fun a => (f a).any p)).bind f := by
  induction xs with
  | nil => simp
  | cons x xs ih =>
    simp only [filterMap_cons]
    split <;>
    · simp only [find?_cons]
      split <;> simp_all

@[simp, grind =] theorem find?_map {f : β → α} {l : List β} : find? p (l.map f) = (l.find? (p ∘ f)).map f := by
  induction l with
  | nil => simp
  | cons x xs ih =>
    simp only [map_cons, find?]
    by_cases h : p (f x) <;> simp [h, ih]

@[simp, grind _=_] theorem find?_flatten {xss : List (List α)} {p : α → Bool} :
    xss.flatten.find? p = xss.findSome? (find? p) := by
  induction xss with
  | nil => simp
  | cons _ _ ih =>
    simp only [flatten_cons, find?_append, findSome?_cons, ih]
    split <;> simp [*]

theorem find?_flatten_eq_none_iff {xs : List (List α)} {p : α → Bool} :
    xs.flatten.find? p = none ↔ ∀ ys ∈ xs, ∀ x ∈ ys, !p x := by
  simp

/--
If `find? p` returns `some a` from `xs.flatten`, then `p a` holds, and
some list in `xs` contains `a`, and no earlier element of that list satisfies `p`.
Moreover, no earlier list in `xs` has an element satisfying `p`.
-/
theorem find?_flatten_eq_some_iff {xs : List (List α)} {p : α → Bool} {a : α} :
    xs.flatten.find? p = some a ↔
      p a ∧ ∃ as ys zs bs, xs = as ++ (ys ++ a :: zs) :: bs ∧
        (∀ l ∈ as, ∀ x ∈ l, !p x) ∧ (∀ x ∈ ys, !p x) := by
  rw [find?_eq_some_iff_append]
  constructor
  · rintro ⟨h, ⟨ys, zs, h₁, h₂⟩⟩
    refine ⟨h, ?_⟩
    rw [flatten_eq_append_iff] at h₁
    obtain (⟨as, bs, rfl, rfl, h₁⟩ | ⟨as, bs, c, cs, ds, rfl, rfl, h₁⟩) := h₁
    · replace h₁ := h₁.symm
      rw [flatten_eq_cons_iff] at h₁
      obtain ⟨bs, cs, ds, rfl, h₁, rfl⟩ := h₁
      refine ⟨as ++ bs, [], cs, ds, by simp, ?_⟩
      simp
      rintro l (ma | mb) x m
      · simpa using h₂ x (by simpa using ⟨l, ma, m⟩)
      · specialize h₁ _ mb
        simp_all
    · simp
      refine ⟨as, bs, ?_⟩
      refine ⟨?_, ?_, ?_⟩
      · simp_all
      · intro l ml a m
        simpa using h₂ a (by simpa using .inl ⟨l, ml, m⟩)
      · intro x m
        simpa using h₂ x (by simpa using .inr m)
  · rintro ⟨h, ⟨as, ys, zs, bs, rfl, h₁, h₂⟩⟩
    refine ⟨h, as.flatten ++ ys, zs ++ bs.flatten, by simp, ?_⟩
    intro a m
    simp at m
    obtain ⟨l, ml, m⟩ | m := m
    · exact h₁ l ml a m
    · exact h₂ a m

@[simp, grind =] theorem find?_flatMap {xs : List α} {f : α → List β} {p : β → Bool} :
    (xs.flatMap f).find? p = xs.findSome? (fun x => (f x).find? p) := by
  simp [flatMap_def, findSome?_map]; rfl

theorem find?_flatMap_eq_none_iff {xs : List α} {f : α → List β} {p : β → Bool} :
    (xs.flatMap f).find? p = none ↔ ∀ x ∈ xs, ∀ y ∈ f x, !p y := by
  simp

@[grind =]
theorem find?_replicate : find? p (replicate n a) = if n = 0 then none else if p a then some a else none := by
  cases n
  · simp
  · by_cases p a <;> simp_all [replicate_succ]

@[simp] theorem find?_replicate_of_length_pos (h : 0 < n) : find? p (replicate n a) = if p a then some a else none := by
  simp [find?_replicate, Nat.ne_of_gt h]

@[simp] theorem find?_replicate_of_pos (h : p a) : find? p (replicate n a) = if n = 0 then none else some a := by
  simp [find?_replicate, h]

@[simp] theorem find?_replicate_of_neg (h : ¬ p a) : find? p (replicate n a) = none := by
  simp [find?_replicate, h]

-- This isn't a `@[simp]` lemma since there is already a lemma for `l.find? p = none` for any `l`.
theorem find?_replicate_eq_none_iff {n : Nat} {a : α} {p : α → Bool} :
    (replicate n a).find? p = none ↔ n = 0 ∨ !p a := by
  simp [Classical.or_iff_not_imp_left]

@[simp] theorem find?_replicate_eq_some_iff {n : Nat} {a b : α} {p : α → Bool} :
    (replicate n a).find? p = some b ↔ n ≠ 0 ∧ p a ∧ a = b := by
  cases n <;> simp

@[simp] theorem get_find?_replicate {n : Nat} {a : α} {p : α → Bool} (h) : ((replicate n a).find? p).get h = a := by
  cases n with
  | zero => simp at h
  | succ n => simp

theorem Sublist.find?_isSome {l₁ l₂ : List α} (h : l₁ <+ l₂) : (l₁.find? p).isSome → (l₂.find? p).isSome := by
  induction h with
  | slnil => simp
  | cons a h ih
  | cons₂ a h ih =>
    simp only [find?]
    split
    · simp
    · simpa using ih

grind_pattern Sublist.find?_isSome => l₁ <+ l₂, l₁.find? p
grind_pattern Sublist.find?_isSome => l₁ <+ l₂, l₂.find? p

theorem Sublist.find?_eq_none {l₁ l₂ : List α} (h : l₁ <+ l₂) : l₂.find? p = none → l₁.find? p = none := by
  simp only [List.find?_eq_none, Bool.not_eq_true]
  exact fun w x m => w x (Sublist.mem m h)

theorem IsPrefix.find?_eq_some {l₁ l₂ : List α} {p : α → Bool} (h : l₁ <+: l₂) :
    List.find? p l₁ = some b → List.find? p l₂ = some b := by
  rw [IsPrefix] at h
  obtain ⟨t, rfl⟩ := h
  simp +contextual [find?_append]

grind_pattern IsPrefix.find?_eq_some => l₁ <+: l₂, l₁.find? p, some b
grind_pattern IsPrefix.find?_eq_some => l₁ <+: l₂, l₂.find? p, some b

theorem IsPrefix.find?_eq_none {l₁ l₂ : List α} {p : α → Bool} (h : l₁ <+: l₂) :
    List.find? p l₂ = none → List.find? p l₁ = none :=
  h.sublist.find?_eq_none

grind_pattern Sublist.find?_eq_none => l₁ <+ l₂, l₂.find? p
grind_pattern Sublist.find?_eq_none => l₁ <+ l₂, l₁.find? p

theorem IsSuffix.find?_eq_none {l₁ l₂ : List α} {p : α → Bool} (h : l₁ <:+ l₂) :
    List.find? p l₂ = none → List.find? p l₁ = none :=
  h.sublist.find?_eq_none

grind_pattern IsPrefix.find?_eq_none => l₁ <+: l₂, l₂.find? p
grind_pattern IsPrefix.find?_eq_none => l₁ <+: l₂, l₁.find? p

theorem IsInfix.find?_eq_none {l₁ l₂ : List α} {p : α → Bool} (h : l₁ <:+: l₂) :
    List.find? p l₂ = none → List.find? p l₁ = none :=
  h.sublist.find?_eq_none

grind_pattern IsSuffix.find?_eq_none => l₁ <:+ l₂, l₂.find? p
grind_pattern IsSuffix.find?_eq_none => l₁ <:+ l₂, l₁.find? p

@[grind =]
theorem find?_pmap {P : α → Prop} {f : (a : α) → P a → β} {xs : List α}
    (H : ∀ (a : α), a ∈ xs → P a) {p : β → Bool} :
    (xs.pmap f H).find? p = (xs.attach.find? (fun ⟨a, m⟩ => p (f a (H a m)))).map fun ⟨a, m⟩ => f a (H a m) := by
  simp only [pmap_eq_map_attach, find?_map]
  rfl

/-! ### findIdx? (preliminary lemmas) -/

@[local simp] private theorem findIdx?_go_nil {p : α → Bool} {i : Nat} :
    findIdx?.go p [] i = none := rfl

@[local simp] private theorem findIdx?_go_cons :
    findIdx?.go p (x :: xs) i = if p x then some i else findIdx?.go p xs (i + 1) := rfl

private theorem findIdx?_go_succ {p : α → Bool} {xs : List α} {i : Nat} :
    findIdx?.go p xs (i+1) = (findIdx?.go p xs i).map fun i => i + 1 := by
  induction xs generalizing i with simp
  | cons _ _ _ => split <;> simp_all

private theorem findIdx?_go_eq {p : α → Bool} {xs : List α} {i : Nat} :
    findIdx?.go p xs (i+1) = (findIdx?.go p xs 0).map fun k => k + (i + 1) := by
  induction xs generalizing i with
  | nil => simp
  | cons _ _ _ =>
    simp only [findIdx?_go_succ, findIdx?_go_cons, Nat.zero_add]
    split
    · simp_all
    · simp_all only [findIdx?_go_succ, Bool.not_eq_true, Option.map_map, Nat.zero_add]
      congr 1
      ext
      simp only [Nat.add_comm i, Function.comp_apply, Nat.add_assoc]

@[simp, grind =] theorem findIdx?_nil : ([] : List α).findIdx? p = none := rfl

@[grind =] theorem findIdx?_cons :
    (x :: xs).findIdx? p = if p x then some 0 else (xs.findIdx? p).map fun i => i + 1 := by
  simp [findIdx?, findIdx?_go_eq]

@[simp] theorem findIdx?_singleton {a : α} {p : α → Bool} : [a].findIdx? p = if p a then some 0 else none := by
  simp [findIdx?_cons, findIdx?_nil]

/-! ### findIdx -/

@[grind =]
theorem findIdx_cons {p : α → Bool} {b : α} {l : List α} :
    (b :: l).findIdx p = bif p b then 0 else (l.findIdx p) + 1 := by
  cases H : p b with
  | true => simp [H, findIdx, findIdx.go]
  | false => simp [H, findIdx, findIdx.go, findIdx_go_succ]
where
  findIdx_go_succ (p : α → Bool) (l : List α) (n : Nat) :
      List.findIdx.go p l (n + 1) = (findIdx.go p l n) + 1 := by
    cases l with
    | nil => unfold findIdx.go; exact Nat.succ_eq_add_one n
    | cons hd tl =>
      unfold findIdx.go
      cases p hd <;> simp only [cond_false, cond_true]
      exact findIdx_go_succ p tl (n + 1)

@[simp] theorem findIdx_singleton {a : α} {p : α → Bool} : [a].findIdx p = if p a then 0 else 1 := by
  simp [findIdx_cons, findIdx_nil]

theorem findIdx_of_getElem?_eq_some {xs : List α} (w : xs[xs.findIdx p]? = some y) : p y := by
  induction xs with
  | nil => simp_all
  | cons x xs ih => by_cases h : p x <;> simp_all [findIdx_cons]

theorem findIdx_getElem {xs : List α} {w : xs.findIdx p < xs.length} :
    p xs[xs.findIdx p] :=
  xs.findIdx_of_getElem?_eq_some (getElem?_eq_getElem w)

grind_pattern findIdx_getElem => xs[xs.findIdx p]

theorem findIdx_lt_length_of_exists {xs : List α} (h : ∃ x ∈ xs, p x) :
    xs.findIdx p < xs.length := by
  induction xs with
  | nil => simp_all
  | cons x xs ih =>
    by_cases p x
    · simp_all only [forall_exists_index, and_imp, mem_cons, exists_eq_or_imp, true_or,
        findIdx_cons, cond_true, length_cons]
      apply Nat.succ_pos
    · simp_all [findIdx_cons, Nat.succ_lt_succ_iff]
      obtain ⟨x', m', h'⟩ := h
      exact ih x' m' h'

theorem findIdx_getElem?_eq_getElem_of_exists {xs : List α} (h : ∃ x ∈ xs, p x) :
    xs[xs.findIdx p]? = some (xs[xs.findIdx p]'(xs.findIdx_lt_length_of_exists h)) :=
  getElem?_eq_getElem (findIdx_lt_length_of_exists h)

@[simp]
theorem findIdx_eq_length {p : α → Bool} {xs : List α} :
    xs.findIdx p = xs.length ↔ ∀ x ∈ xs, p x = false := by
  induction xs with
  | nil => simp_all
  | cons x xs ih =>
    rw [findIdx_cons, length_cons]
    simp only [cond_eq_ite]
    split <;> simp_all

theorem findIdx_eq_length_of_false {p : α → Bool} {xs : List α} (h : ∀ x ∈ xs, p x = false) :
    xs.findIdx p = xs.length := by
  rw [findIdx_eq_length]
  exact h

theorem findIdx_le_length {p : α → Bool} {xs : List α} : xs.findIdx p ≤ xs.length := by
  by_cases e : ∃ x ∈ xs, p x
  · exact Nat.le_of_lt (findIdx_lt_length_of_exists e)
  · simp at e
    exact Nat.le_of_eq (findIdx_eq_length.mpr e)

grind_pattern findIdx_le_length => xs.findIdx p, xs.length

@[simp]
theorem findIdx_lt_length {p : α → Bool} {xs : List α} :
    xs.findIdx p < xs.length ↔ ∃ x ∈ xs, p x := by
  rw [← Decidable.not_iff_not, Nat.not_lt]
  have := @Nat.le_antisymm_iff (xs.findIdx p) xs.length
  simp only [findIdx_le_length, true_and] at this
  rw [← this, findIdx_eq_length, not_exists]
  simp only [Bool.not_eq_true, not_and]

grind_pattern findIdx_lt_length => xs.findIdx p, xs.length

/-- `p` does not hold for elements with indices less than `xs.findIdx p`. -/
theorem not_of_lt_findIdx {p : α → Bool} {xs : List α} {i : Nat} (h : i < xs.findIdx p) :
    p (xs[i]'(Nat.le_trans h findIdx_le_length)) = false := by
  revert i
  induction xs with
  | nil => intro i h; rw [findIdx_nil] at h; simp at h
  | cons x xs ih =>
    intro i h
    have ho := h
    rw [findIdx_cons] at h
    have npx : p x = false := by
      apply eq_false_of_ne_true
      intro y
      rw [y, cond_true] at h
      simp at h
    simp [npx, cond_false] at h
    cases i.eq_zero_or_pos with
    | inl e => simpa [e, Fin.zero_eta, get_cons_zero]
    | inr e =>
      have ipm := Nat.succ_pred_eq_of_pos e
      simp +singlePass only [← ipm, getElem_cons_succ]
      rw [← ipm, Nat.succ_lt_succ_iff] at h
      simpa using ih h

grind_pattern not_of_lt_findIdx => xs.findIdx p, xs[i]

/-- If `¬ p xs[j]` for all `j < i`, then `i ≤ xs.findIdx p`. -/
theorem le_findIdx_of_not {p : α → Bool} {xs : List α} {i : Nat} (h : i < xs.length)
    (h2 : ∀ j (hji : j < i), p (xs[j]'(Nat.lt_trans hji h)) = false) : i ≤ xs.findIdx p := by
  apply Decidable.byContradiction
  intro f
  simp only [Nat.not_le] at f
  exact absurd (@findIdx_getElem _ p xs (Nat.lt_trans f h)) (by simpa using h2 (xs.findIdx p) f)

/-- If `¬ p xs[j]` for all `j ≤ i`, then `i < xs.findIdx p`. -/
theorem lt_findIdx_of_not {p : α → Bool} {xs : List α} {i : Nat} (h : i < xs.length)
    (h2 : ∀ j (hji : j ≤ i), ¬p (xs[j]'(Nat.lt_of_le_of_lt hji h))) : i < xs.findIdx p := by
  apply Decidable.byContradiction
  intro f
  simp only [Nat.not_lt] at f
  exact absurd (@findIdx_getElem _ p xs (Nat.lt_of_le_of_lt f h)) (h2 (xs.findIdx p) f)

/-- `xs.findIdx p = i` iff `p xs[i]` and `¬ p xs [j]` for all `j < i`. -/
theorem findIdx_eq {p : α → Bool} {xs : List α} {i : Nat} (h : i < xs.length) :
    xs.findIdx p = i ↔ p xs[i] ∧ ∀ j (hji : j < i), p (xs[j]'(Nat.lt_trans hji h)) = false := by
  refine ⟨fun f ↦ ⟨f ▸ (@findIdx_getElem _ p xs (f ▸ h)), fun _ hji ↦ not_of_lt_findIdx (f ▸ hji)⟩,
    fun ⟨_, h2⟩ ↦ ?_⟩
  apply Nat.le_antisymm _ (le_findIdx_of_not h h2)
  apply Decidable.byContradiction
  intro h3
  simp at h3
  simp_all [not_of_lt_findIdx h3]

@[simp]
theorem lt_findIdx_iff (xs : List α) (p : α → Bool) (i : Nat) :
    i < xs.findIdx p ↔ ∃ h : i < xs.length, ∀ j, (hj : j ≤ i) → p xs[j] = false :=
  ⟨fun h => ⟨by have := findIdx_le_length (xs := xs) (p := p); omega,
    fun j hj => by apply not_of_lt_findIdx; omega⟩,
    fun ⟨h, w⟩ => by apply lt_findIdx_of_not h; simpa using w⟩

@[simp, grind =]
theorem findIdx_map (xs : List α) (f : α → β) (p : β → Bool) :
    (xs.map f).findIdx p = xs.findIdx (p ∘ f) := by
  induction xs with simp_all [findIdx_cons]

@[grind =]
theorem findIdx_append {p : α → Bool} {l₁ l₂ : List α} :
    (l₁ ++ l₂).findIdx p =
      if l₁.findIdx p < l₁.length then l₁.findIdx p else l₂.findIdx p + l₁.length := by
  induction l₁ with
  | nil => simp
  | cons x xs ih =>
    simp only [findIdx_cons, length_cons, cons_append]
    by_cases h : p x
    · simp [h]
    · simp only [h, ih, cond_eq_ite, Bool.false_eq_true, ↓reduceIte, add_one_lt_add_one_iff]
      split <;> simp [Nat.add_assoc]

theorem IsPrefix.findIdx_le {l₁ l₂ : List α} {p : α → Bool} (h : l₁ <+: l₂) :
    l₁.findIdx p ≤ l₂.findIdx p := by
  rw [IsPrefix] at h
  obtain ⟨t, rfl⟩ := h
  simp only [findIdx_append, findIdx_lt_length]
  split
  · exact Nat.le_refl ..
  · simp_all [findIdx_eq_length_of_false]

grind_pattern IsPrefix.findIdx_le => l₁ <:+ l₂, l₁.findIdx p
grind_pattern IsPrefix.findIdx_le => l₁ <:+ l₂, l₂.findIdx p

theorem IsPrefix.findIdx_eq_of_findIdx_lt_length {l₁ l₂ : List α} {p : α → Bool} (h : l₁ <+: l₂)
    (lt : l₁.findIdx p < l₁.length) : l₂.findIdx p = l₁.findIdx p := by
  rw [IsPrefix] at h
  obtain ⟨t, rfl⟩ := h
  simp only [findIdx_append, findIdx_lt_length]
  split
  · rfl
  · simp_all

grind_pattern IsPrefix.findIdx_eq_of_findIdx_lt_length => l₁ <:+ l₂, l₁.findIdx p, l₂.findIdx p

theorem findIdx_le_findIdx {l : List α} {p q : α → Bool} (h : ∀ x ∈ l, p x → q x) : l.findIdx q ≤ l.findIdx p := by
  induction l with
  | nil => simp
  | cons x xs ih =>
    simp only [findIdx_cons, cond_eq_ite]
    split
    · simp
    · split
      · simp_all
      · simp only [Nat.add_le_add_iff_right]
        exact ih fun _ m w => h _ (mem_cons_of_mem x m) w

@[simp] theorem findIdx_subtype {p : α → Prop} {l : List { x // p x }}
    {f : { x // p x } → Bool} {g : α → Bool} (hf : ∀ x h, f ⟨x, h⟩ = g x) :
    l.findIdx f = l.unattach.findIdx g := by
  unfold unattach
  induction l with
  | nil => simp
  | cons a l ih =>
    simp [ih, hf, findIdx_cons]

/-! ### findIdx? -/

@[simp, grind =]
theorem findIdx?_eq_none_iff {xs : List α} {p : α → Bool} :
    xs.findIdx? p = none ↔ ∀ x, x ∈ xs → p x = false := by
  induction xs with
  | nil => simp_all
  | cons x xs ih =>
    simp only [findIdx?_cons]
    split <;> simp_all

@[simp, grind =]
theorem findIdx?_isSome {xs : List α} {p : α → Bool} :
    (xs.findIdx? p).isSome = xs.any p := by
  induction xs with
  | nil => simp
  | cons x xs ih =>
    simp only [findIdx?_cons]
    split <;> simp_all

@[simp, grind =]
theorem findIdx?_isNone {xs : List α} {p : α → Bool} :
    (xs.findIdx? p).isNone = xs.all (¬p ·) := by
  induction xs with
  | nil => simp
  | cons x xs ih =>
    simp only [findIdx?_cons]
    split <;> simp_all

theorem findIdx?_eq_some_iff_findIdx_eq {xs : List α} {p : α → Bool} {i : Nat} :
    xs.findIdx? p = some i ↔ i < xs.length ∧ xs.findIdx p = i := by
  induction xs generalizing i with
  | nil => simp_all
  | cons x xs ih =>
    simp only [findIdx?_cons, findIdx_cons]
    split
    · simp_all [cond_eq_ite]
      rintro rfl
      exact zero_lt_succ xs.length
    · simp_all [cond_eq_ite, and_assoc]
      constructor
      · rintro ⟨a, lt, rfl, rfl⟩
        simp_all [Nat.succ_lt_succ_iff]
      · rintro ⟨h, rfl⟩
        exact ⟨_, by simp_all [Nat.succ_lt_succ_iff], rfl, rfl⟩

theorem findIdx?_eq_some_of_exists {xs : List α} {p : α → Bool} (h : ∃ x, x ∈ xs ∧ p x) :
    xs.findIdx? p = some (xs.findIdx p) := by
  rw [findIdx?_eq_some_iff_findIdx_eq]
  exact ⟨findIdx_lt_length_of_exists h, rfl⟩

theorem findIdx?_eq_none_iff_findIdx_eq {xs : List α} {p : α → Bool} :
    xs.findIdx? p = none ↔ xs.findIdx p = xs.length := by
  simp

theorem findIdx?_eq_guard_findIdx_lt {xs : List α} {p : α → Bool} :
    xs.findIdx? p = Option.guard (fun i => i < xs.length) (xs.findIdx p) := by
  match h : xs.findIdx? p with
  | none =>
    simp only [findIdx?_eq_none_iff] at h
    simp [findIdx_eq_length_of_false h, Option.guard]
  | some i =>
    simp only [findIdx?_eq_some_iff_findIdx_eq] at h
    simp [h]

theorem findIdx?_eq_some_iff_getElem {xs : List α} {p : α → Bool} {i : Nat} :
    xs.findIdx? p = some i ↔
      ∃ h : i < xs.length, p xs[i] ∧ ∀ j (hji : j < i), ¬p (xs[j]'(Nat.lt_trans hji h)) := by
  induction xs generalizing i with
  | nil => simp
  | cons x xs ih =>
    simp only [findIdx?_cons]
    split
    · simp only [Option.some.injEq, Bool.not_eq_true, length_cons]
      cases i with
      | zero => simp_all
      | succ i =>
        simp only [zero_ne_add_one, getElem_cons_succ, false_iff, not_exists,
          not_and, Classical.not_forall, Bool.not_eq_false]
        intros
        refine ⟨0, zero_lt_succ i, ‹_›⟩
    · simp only [Option.map_eq_some_iff, ih, Bool.not_eq_true, length_cons]
      constructor
      · rintro ⟨a, ⟨⟨h, h₁, h₂⟩, rfl⟩⟩
        refine ⟨Nat.succ_lt_succ_iff.mpr h, by simpa, fun j hj => ?_⟩
        cases j with
        | zero => simp_all
        | succ j =>
          apply h₂
          simp_all [Nat.succ_lt_succ_iff]
      · rintro ⟨h, h₁, h₂⟩
        cases i with
        | zero => simp_all
        | succ i =>
          refine ⟨i, ⟨Nat.succ_lt_succ_iff.mp h, by simpa, fun j hj => ?_⟩, rfl⟩
          simpa using h₂ (j + 1) (Nat.succ_lt_succ_iff.mpr hj)

theorem of_findIdx?_eq_some {xs : List α} {p : α → Bool} (w : xs.findIdx? p = some i) :
    match xs[i]? with | some a => p a | none => false := by
  induction xs generalizing i with
  | nil => simp_all
  | cons x xs ih =>
    simp_all only [findIdx?_cons]
    split at w <;> cases i <;> simp_all

theorem of_findIdx?_eq_none {xs : List α} {p : α → Bool} (w : xs.findIdx? p = none) :
    ∀ i : Nat, match xs[i]? with | some a => ¬ p a | none => true := by
  intro i
  induction xs generalizing i with
  | nil => simp_all
  | cons x xs ih =>
    simp_all only [Bool.not_eq_true, findIdx?_cons]
    cases i with
    | zero =>
      split at w <;> simp_all
    | succ i =>
      simp only [getElem?_cons_succ]
      apply ih
      split at w <;> simp_all

@[simp, grind _=_] theorem findIdx?_map {f : β → α} {l : List β} : findIdx? p (l.map f) = l.findIdx? (p ∘ f) := by
  induction l with
  | nil => simp
  | cons x xs ih =>
    simp only [map_cons, findIdx?_cons]
    split <;> simp_all

@[simp, grind =] theorem findIdx?_append :
    (xs ++ ys : List α).findIdx? p =
      (xs.findIdx? p).or ((ys.findIdx? p).map fun i => i + xs.length) := by
  induction xs with simp [findIdx?_cons]
  | cons _ _ _ => split <;> simp_all [Option.map_or, Option.map_map]; rfl

theorem findIdx?_flatten {l : List (List α)} {p : α → Bool} :
    l.flatten.findIdx? p =
      (l.findIdx? (·.any p)).map
        fun i => ((l.take i).map List.length).sum +
          (l[i]?.map fun xs => xs.findIdx p).getD 0 := by
  induction l with
  | nil => simp
  | cons xs l ih =>
    rw [flatten_cons, findIdx?_append, ih, findIdx?_cons]
    split <;> rename_i h
    · simp only [any_eq_true] at h
      rw [Option.or_of_isSome (by simp_all [findIdx?_isSome])]
      simp_all [findIdx?_eq_some_of_exists]
    · rw [Option.or_of_isNone (by simp_all [findIdx?_isNone])]
      simp [Function.comp_def, Nat.add_comm, Nat.add_assoc]

@[simp, grind =] theorem findIdx?_replicate :
    (replicate n a).findIdx? p = if 0 < n ∧ p a then some 0 else none := by
  cases n with
  | zero => simp
  | succ n =>
    simp only [replicate, findIdx?_cons, zero_lt_succ, true_and]
    split <;> simp_all

theorem findIdx?_eq_findSome?_zipIdx {xs : List α} {p : α → Bool} :
    xs.findIdx? p = xs.zipIdx.findSome? fun ⟨a, i⟩ => if p a then some i else none := by
  induction xs with
  | nil => simp
  | cons x xs ih =>
    simp only [findIdx?_cons, Nat.zero_add, zipIdx]
    split
    · simp_all
    · simp_all only [ite_false, Option.isNone_none, findSome?_cons_of_isNone, reduceCtorEq]
      rw [← map_snd_add_zipIdx_eq_zipIdx (n := 1) (k := 0)]
      simp [Function.comp_def, findSome?_map]

theorem findIdx?_eq_fst_find?_zipIdx {xs : List α} {p : α → Bool} :
    xs.findIdx? p = (xs.zipIdx.find? fun ⟨x, _⟩ => p x).map (·.2) := by
  induction xs with
  | nil => simp
  | cons x xs ih =>
    simp only [findIdx?_cons, Nat.zero_add, zipIdx_cons]
    split
    · simp_all
    · rw [ih, ← map_snd_add_zipIdx_eq_zipIdx (n := 1) (k := 0)]
      simp [Function.comp_def, *]

-- See also `findIdx_le_findIdx`.
theorem findIdx?_eq_none_of_findIdx?_eq_none {xs : List α} {p q : α → Bool} (w : ∀ x ∈ xs, p x → q x) :
    xs.findIdx? q = none → xs.findIdx? p = none := by
  simp only [findIdx?_eq_none_iff]
  intro h x m
  cases z : p x
  · rfl
  · exfalso
    specialize w x m z
    specialize h x m
    simp_all

theorem Sublist.findIdx?_isSome {l₁ l₂ : List α} (h : l₁ <+ l₂) :
    (l₁.findIdx? p).isSome → (l₂.findIdx? p).isSome := by
  simp only [List.findIdx?_isSome, any_eq_true]
  rintro ⟨w, m, q⟩
  exact ⟨w, h.mem m, q⟩

theorem Sublist.findIdx?_eq_none {l₁ l₂ : List α} (h : l₁ <+ l₂) :
    l₂.findIdx? p = none → l₁.findIdx? p = none := by
  simp only [findIdx?_eq_none_iff]
  exact fun w x m => w x (h.mem m)

grind_pattern Sublist.findIdx?_eq_none => l₁ <+ l₂, l₁.findIdx? p
grind_pattern Sublist.findIdx?_eq_none => l₁ <+ l₂, l₂.findIdx? p

theorem IsPrefix.findIdx?_eq_some {l₁ l₂ : List α} {p : α → Bool} (h : l₁ <+: l₂) :
    List.findIdx? p l₁ = some i → List.findIdx? p l₂ = some i := by
  rw [IsPrefix] at h
  obtain ⟨t, rfl⟩ := h
  intro h
  simp [findIdx?_append, h]

theorem IsPrefix.findIdx?_eq_none {l₁ l₂ : List α} {p : α → Bool} (h : l₁ <+: l₂) :
    List.findIdx? p l₂ = none → List.findIdx? p l₁ = none :=
  h.sublist.findIdx?_eq_none

grind_pattern IsPrefix.findIdx?_eq_none => l₁ <+: l₂, l₁.findIdx? p
grind_pattern IsPrefix.findIdx?_eq_none => l₁ <+: l₂, l₂.findIdx? p

theorem IsSuffix.findIdx?_eq_none {l₁ l₂ : List α} {p : α → Bool} (h : l₁ <:+ l₂) :
    List.findIdx? p l₂ = none → List.findIdx? p l₁ = none :=
  h.sublist.findIdx?_eq_none

grind_pattern IsSuffix.findIdx?_eq_none => l₁ <:+ l₂, l₁.findIdx? p
grind_pattern IsSuffix.findIdx?_eq_none => l₁ <:+ l₂, l₂.findIdx? p

theorem IsInfix.findIdx?_eq_none {l₁ l₂ : List α} {p : α → Bool} (h : l₁ <:+: l₂) :
    List.findIdx? p l₂ = none → List.findIdx? p l₁ = none :=
  h.sublist.findIdx?_eq_none

grind_pattern IsInfix.findIdx?_eq_none => l₁ <:+: l₂, l₁.findIdx? p
grind_pattern IsInfix.findIdx?_eq_none => l₁ <:+: l₂, l₂.findIdx? p

@[grind =]
theorem findIdx_eq_getD_findIdx? {xs : List α} {p : α → Bool} :
    xs.findIdx p = (xs.findIdx? p).getD xs.length := by
  induction xs with
  | nil => simp
  | cons x xs ih =>
    simp only [findIdx_cons, findIdx?_cons]
    split <;> simp_all

@[simp] theorem findIdx?_subtype {p : α → Prop} {l : List { x // p x }}
    {f : { x // p x } → Bool} {g : α → Bool} (hf : ∀ x h, f ⟨x, h⟩ = g x) :
    l.findIdx? f = l.unattach.findIdx? g := by
  unfold unattach
  induction l with
  | nil => simp
  | cons a l ih =>
    simp [hf, findIdx?_cons]
    split <;> simp [ih]

/-! ### findFinIdx? -/

@[simp, grind =] theorem findFinIdx?_nil {p : α → Bool} : findFinIdx? p [] = none := rfl

theorem findIdx?_go_eq_map_findFinIdx?_go_val {xs : List α} {p : α → Bool} {i : Nat} {h} :
    List.findIdx?.go p xs i =
      (List.findFinIdx?.go p l xs i h).map (·.val) := by
  unfold findIdx?.go
  unfold findFinIdx?.go
  split
  · simp_all
  · simp only
    split
    · simp
    · rw [findIdx?_go_eq_map_findFinIdx?_go_val]

theorem findIdx?_eq_map_findFinIdx?_val {xs : List α} {p : α → Bool} :
    xs.findIdx? p = (xs.findFinIdx? p).map (·.val) := by
  simp [findIdx?, findFinIdx?]
  rw [findIdx?_go_eq_map_findFinIdx?_go_val]

theorem findFinIdx?_eq_pmap_findIdx? {xs : List α} {p : α → Bool} :
    xs.findFinIdx? p =
      (xs.findIdx? p).pmap
        (fun i m => by simp [findIdx?_eq_some_iff_getElem] at m; exact ⟨i, m.choose⟩)
        (fun i h => h) := by
  simp [findIdx?_eq_map_findFinIdx?_val, Option.pmap_map]

@[grind =]
theorem findFinIdx?_cons {p : α → Bool} {x : α} {xs : List α} :
    findFinIdx? p (x :: xs) = if p x then some 0 else (findFinIdx? p xs).map Fin.succ := by
  rw [← Option.map_inj_right (f := Fin.val) (fun a b => Fin.eq_of_val_eq)]
  rw [← findIdx?_eq_map_findFinIdx?_val]
  rw [findIdx?_cons]
  split
  · simp
  · rw [findIdx?_eq_map_findFinIdx?_val]
    simp [Function.comp_def]

@[grind =]
theorem findFinIdx?_append {xs ys : List α} {p : α → Bool} :
    (xs ++ ys).findFinIdx? p =
      ((xs.findFinIdx? p).map (Fin.castLE (by simp))).or
        ((ys.findFinIdx? p).map (Fin.natAdd xs.length) |>.map (Fin.cast (by simp))) := by
  simp only [findFinIdx?_eq_pmap_findIdx?, findIdx?_append, Option.pmap_or]
  split <;> rename_i h _
  · simp [h, Option.pmap_map, Option.map_pmap, Nat.add_comm]
  · simp [h]

@[simp, grind =] theorem findFinIdx?_singleton {a : α} {p : α → Bool} :
    [a].findFinIdx? p = if p a then some ⟨0, by simp⟩ else none := by
  simp [findFinIdx?_cons, findFinIdx?_nil]

@[simp, grind =] theorem findFinIdx?_eq_none_iff {l : List α} {p : α → Bool} :
    l.findFinIdx? p = none ↔ ∀ x ∈ l, ¬ p x := by
  simp [findFinIdx?_eq_pmap_findIdx?]

@[simp]
theorem findFinIdx?_eq_some_iff {xs : List α} {p : α → Bool} {i : Fin xs.length} :
    xs.findFinIdx? p = some i ↔
      p xs[i] ∧ ∀ j (hji : j < i), ¬p (xs[j]'(Nat.lt_trans hji i.2)) := by
  simp only [findFinIdx?_eq_pmap_findIdx?, Option.pmap_eq_some_iff, findIdx?_eq_some_iff_getElem,
    Bool.not_eq_true, exists_and_left, and_exists_self, Fin.getElem_fin]
  constructor
  · rintro ⟨a, ⟨h, w₁, w₂⟩, rfl⟩
    exact ⟨w₁, fun j hji => by simpa using w₂ j hji⟩
  · rintro ⟨h, w⟩
    exact ⟨i, ⟨i.2, h, fun j hji => w ⟨j, by omega⟩ hji⟩, rfl⟩

@[simp, grind =]
theorem isSome_findFinIdx? {l : List α} {p : α → Bool} :
    (l.findFinIdx? p).isSome = l.any p := by
  induction l with
  | nil => simp
  | cons x xs ih =>
    simp only [findFinIdx?_cons]
    split <;> simp_all

@[simp, grind =]
theorem isNone_findFinIdx? {l : List α} {p : α → Bool} :
    (l.findFinIdx? p).isNone = l.all (fun x => ¬ p x) := by
  induction l with
  | nil => simp
  | cons x xs ih =>
    simp only [findFinIdx?_cons]
    split <;> simp_all

@[simp] theorem findFinIdx?_subtype {p : α → Prop} {l : List { x // p x }}
    {f : { x // p x } → Bool} {g : α → Bool} (hf : ∀ x h, f ⟨x, h⟩ = g x) :
    l.findFinIdx? f = (l.unattach.findFinIdx? g).map (fun i => i.cast (by simp)) := by
  induction l with
  | nil => simp
  | cons a l ih =>
    simp [hf, findFinIdx?_cons]
    split <;> simp [ih, Function.comp_def]


/-! ### idxOf

The verification API for `idxOf` is still incomplete.
The lemmas below should be made consistent with those for `findIdx` (and proved using them).
-/

@[grind =]
theorem idxOf_cons [BEq α] :
    (x :: xs : List α).idxOf y = bif x == y then 0 else xs.idxOf y + 1 := by
  dsimp [idxOf]
  simp [findIdx_cons]

@[simp] theorem idxOf_cons_self [BEq α] [ReflBEq α] {l : List α} : (a :: l).idxOf a = 0 := by
  simp [idxOf_cons]

@[grind =]
theorem idxOf_append [BEq α] [LawfulBEq α] {l₁ l₂ : List α} {a : α} :
    (l₁ ++ l₂).idxOf a = if a ∈ l₁ then l₁.idxOf a else l₂.idxOf a + l₁.length := by
  rw [idxOf, findIdx_append]
  split <;> rename_i h
  · rw [if_pos]
    simpa using h
  · rw [if_neg]
    simpa using h



theorem idxOf_eq_length [BEq α] [LawfulBEq α] {l : List α} (h : a ∉ l) : l.idxOf a = l.length := by
  induction l with
  | nil => rfl
  | cons x xs ih =>
    simp only [mem_cons, not_or] at h
    simp only [idxOf_cons, cond_eq_ite, beq_iff_eq]
    split <;> simp_all



theorem idxOf_lt_length_of_mem [BEq α] [EquivBEq α] {l : List α} (h : a ∈ l) : l.idxOf a < l.length := by
  induction l with
  | nil => simp at h
  | cons x xs ih =>
    simp only [mem_cons] at h
    obtain rfl | h := h
    · simp
    · simp only [idxOf_cons, cond_eq_ite, length_cons]
      specialize ih h
      split
      · exact zero_lt_succ xs.length
      · exact Nat.add_lt_add_right ih 1

theorem idxOf_le_length [BEq α] [LawfulBEq α] {l : List α} {a : α} :
    l.idxOf a ≤ l.length := by
  simpa [idxOf] using findIdx_le_length

grind_pattern idxOf_le_length => l.idxOf a, l.length

theorem idxOf_lt_length_iff [BEq α] [LawfulBEq α] {l : List α} {a : α} :
    l.idxOf a < l.length ↔ a ∈ l := by
  constructor
  · intro h
    simpa [idxOf] using h
  · exact idxOf_lt_length_of_mem

grind_pattern idxOf_lt_length_iff => l.idxOf a, l.length



/-! ### finIdxOf?

The verification API for `finIdxOf?` is still incomplete.
The lemmas below should be made consistent with those for `findFinIdx?` (and proved using them).
-/

theorem idxOf?_eq_map_finIdxOf?_val [BEq α] {xs : List α} {a : α} :
    xs.idxOf? a = (xs.finIdxOf? a).map (·.val) := by
  simp [idxOf?, finIdxOf?, findIdx?_eq_map_findFinIdx?_val]

@[simp, grind =] theorem finIdxOf?_nil [BEq α] : ([] : List α).finIdxOf? a = none := rfl

@[grind =] theorem finIdxOf?_cons [BEq α] {a : α} {xs : List α} :
    (a :: xs).finIdxOf? b =
      if a == b then some ⟨0, by simp⟩ else (xs.finIdxOf? b).map (·.succ) := by
  simp [finIdxOf?, findFinIdx?_cons]

@[simp, grind =] theorem finIdxOf?_eq_none_iff [BEq α] [LawfulBEq α] {l : List α} {a : α} :
    l.finIdxOf? a = none ↔ a ∉ l := by
  simp only [finIdxOf?, findFinIdx?_eq_none_iff, beq_iff_eq]
  constructor
  · intro w m
    exact w a m rfl
  · rintro h a m rfl
    exact h m

@[simp] theorem finIdxOf?_eq_some_iff [BEq α] [LawfulBEq α] {l : List α} {a : α} {i : Fin l.length} :
    l.finIdxOf? a = some i ↔ l[i] = a ∧ ∀ j (_ : j < i), ¬l[j] = a := by
  simp only [finIdxOf?, findFinIdx?_eq_some_iff, beq_iff_eq]

@[simp, grind =]
theorem isSome_finIdxOf? [BEq α] [PartialEquivBEq α] {l : List α} {a : α} :
    (l.finIdxOf? a).isSome = l.contains a := by
  induction l with
  | nil => simp
  | cons x xs ih =>
    simp only [finIdxOf?_cons]
    split <;> simp_all [BEq.comm]

@[simp]
theorem isNone_finIdxOf? [BEq α] [PartialEquivBEq α] {l : List α} {a : α} :
    (l.finIdxOf? a).isNone = !l.contains a := by
  rw [← isSome_finIdxOf?, Option.not_isSome]

/-! ### idxOf?

The verification API for `idxOf?` is still incomplete.
The lemmas below should be made consistent with those for `findIdx?` (and proved using them).
-/

@[simp, grind =] theorem idxOf?_nil [BEq α] : ([] : List α).idxOf? a = none := rfl

@[grind =] theorem idxOf?_cons [BEq α] {a : α} {xs : List α} {b : α} :
    (a :: xs).idxOf? b = if a == b then some 0 else (xs.idxOf? b).map (· + 1) := by
  simp [idxOf?, findIdx?_cons]

@[simp] theorem idxOf?_singleton [BEq α] {a b : α} : [a].idxOf? b = if a == b then some 0 else none := by
  simp [idxOf?_cons, idxOf?_nil]

@[simp, grind =] theorem idxOf?_eq_none_iff [BEq α] [LawfulBEq α] {l : List α} {a : α} :
    l.idxOf? a = none ↔ a ∉ l := by
  simp only [idxOf?, findIdx?_eq_none_iff, beq_eq_false_iff_ne, ne_eq]
  constructor
  · intro w h
    specialize w _ h
    simp at w
  · rintro w x h rfl
    contradiction



@[simp, grind =]
theorem isSome_idxOf? [BEq α] [LawfulBEq α] {l : List α} {a : α} :
    (l.idxOf? a).isSome ↔ a ∈ l := by
  induction l with
  | nil => simp
  | cons x xs ih =>
    simp only [idxOf?_cons]
    split <;> simp_all [@eq_comm _ x a]

@[grind =]
theorem isNone_idxOf? [BEq α] [LawfulBEq α] {l : List α} {a : α} :
    (l.idxOf? a).isNone = ¬ a ∈ l := by
  simp

/-! ### lookup -/

section lookup
variable [BEq α]

@[simp] theorem lookup_cons_self [ReflBEq α] {k : α} : ((k,b) :: es).lookup k = some b := by
  simp [lookup_cons]

@[simp] theorem lookup_singleton {a b : α} : [(a,b)].lookup c = if c == a then some b else none := by
  simp [lookup_cons, lookup_nil]
  split <;> simp_all

theorem lookup_eq_findSome? {l : List (α × β)} {k : α} :
    l.lookup k = l.findSome? fun p => if k == p.1 then some p.2 else none := by
  induction l with
  | nil => rfl
  | cons p l ih =>
    match p with
    | (k', v) =>
      simp only [lookup_cons, findSome?_cons]
      split <;> simp_all

@[simp, grind =] theorem lookup_eq_none_iff {l : List (α × β)} {k : α} :
    l.lookup k = none ↔ ∀ p ∈ l, k != p.1 := by
  simp [lookup_eq_findSome?, bne]

@[simp] theorem lookup_isSome_iff {l : List (α × β)} {k : α} :
    (l.lookup k).isSome ↔ ∃ p ∈ l, k == p.1 := by
  simp [lookup_eq_findSome?]

theorem lookup_eq_some_iff [LawfulBEq α] {l : List (α × β)} {k : α} {b : β} :
    l.lookup k = some b ↔ ∃ l₁ l₂, l = l₁ ++ (k, b) :: l₂ ∧ ∀ p ∈ l₁, k != p.1 := by
  simp only [lookup_eq_findSome?, findSome?_eq_some_iff]
  constructor
  · rintro ⟨l₁, a, l₂, rfl, h₁, h₂⟩
    simp only [beq_iff_eq, Option.ite_none_right_eq_some, Option.some.injEq] at h₁
    obtain ⟨rfl, rfl⟩ := h₁
    simp at h₂
    exact ⟨l₁, l₂, rfl, by simpa using h₂⟩
  · rintro ⟨l₁, l₂, rfl, h⟩
    exact ⟨l₁, (k, b), l₂, rfl, by simp, by simpa using h⟩

@[grind =]
theorem lookup_append {l₁ l₂ : List (α × β)} {k : α} :
    (l₁ ++ l₂).lookup k = (l₁.lookup k).or (l₂.lookup k) := by
  simp [lookup_eq_findSome?, findSome?_append]

@[grind =]
theorem lookup_replicate {k : α} :
    (replicate n (a,b)).lookup k = if n = 0 then none else if k == a then some b else none := by
  induction n with
  | zero => simp
  | succ n ih =>
    simp only [replicate_succ, lookup_cons]
    split <;> simp_all

theorem lookup_replicate_of_pos {k : α} (h : 0 < n) :
    (replicate n (a, b)).lookup k = if k == a then some b else none := by
  simp [lookup_replicate, Nat.ne_of_gt h]

theorem lookup_replicate_self [ReflBEq α] {a : α} :
    (replicate n (a, b)).lookup a = if n = 0 then none else some b := by
  simp [lookup_replicate]

@[simp] theorem lookup_replicate_self_of_pos [ReflBEq α] {a : α} (h : 0 < n) :
    (replicate n (a, b)).lookup a = some b := by
  simp [lookup_replicate_self, Nat.ne_of_gt h]

@[simp] theorem lookup_replicate_ne {k : α} (h : !k == a) :
    (replicate n (a, b)).lookup k = none := by
  simp_all [lookup_replicate]

theorem Sublist.lookup_isSome {l₁ l₂ : List (α × β)} (h : l₁ <+ l₂) :
    (l₁.lookup k).isSome → (l₂.lookup k).isSome := by
  simp only [lookup_eq_findSome?]
  exact h.findSome?_isSome

theorem Sublist.lookup_eq_none {l₁ l₂ : List (α × β)} (h : l₁ <+ l₂) :
    l₂.lookup k = none → l₁.lookup k = none := by
  simp only [lookup_eq_findSome?]
  exact h.findSome?_eq_none

grind_pattern Sublist.lookup_isSome => l₁ <+ l₂, l₁.lookup k
grind_pattern Sublist.lookup_isSome => l₁ <+ l₂, l₂.lookup k

theorem IsPrefix.lookup_eq_some {l₁ l₂ : List (α × β)} (h : l₁ <+: l₂) :
    List.lookup k l₁ = some b → List.lookup k l₂ = some b := by
  simp only [lookup_eq_findSome?]
  exact h.findSome?_eq_some

theorem IsPrefix.lookup_eq_none {l₁ l₂ : List (α × β)} (h : l₁ <+: l₂) :
    List.lookup k l₂ = none → List.lookup k l₁ = none :=
  h.sublist.lookup_eq_none

grind_pattern IsPrefix.lookup_eq_none => l₁ <+: l₂, l₁.lookup k
grind_pattern IsPrefix.lookup_eq_none => l₁ <+: l₂, l₂.lookup k

theorem IsSuffix.lookup_eq_none {l₁ l₂ : List (α × β)} (h : l₁ <:+ l₂) :
    List.lookup k l₂ = none → List.lookup k l₁ = none :=
  h.sublist.lookup_eq_none

grind_pattern IsSuffix.lookup_eq_none => l₁ <:+ l₂, l₁.lookup k
grind_pattern IsSuffix.lookup_eq_none => l₁ <:+ l₂, l₂.lookup k

theorem IsInfix.lookup_eq_none {l₁ l₂ : List (α × β)} (h : l₁ <:+: l₂) :
    List.lookup k l₂ = none → List.lookup k l₁ = none :=
  h.sublist.lookup_eq_none

grind_pattern IsInfix.lookup_eq_none => l₁ <:+: l₂, l₁.lookup k
grind_pattern IsInfix.lookup_eq_none => l₁ <:+: l₂, l₂.lookup k

end lookup

end List
